﻿using System;
using System.IO;
using System.IO.IsolatedStorage;
using System.Text;
using Microsoft.Phone.Info;
using System.Net.NetworkInformation;
using Microsoft.Phone.Tasks;

namespace Devv.Core.WPErrorManager
{
	public class ErrorManager
	{
		#region Variables

		private const String SEPARATOR = "-----------------------------";

		#endregion

		#region Properties

		/// <summary>
		/// Errors logged since the app has started (current session count).
		/// </summary>
		public static Int32 SessionCount { get; private set; }

		/// <summary>
		/// Errors logged inside the errors folder (total count).
		/// </summary>
		public static Int32 TotalCount
		{
			get
			{
				using (IsolatedStorageFile file = IsolatedStorageFile.GetUserStoreForApplication())
				{
					if (file.DirectoryExists(Config.BasePath))
					{
						return file.GetFileNames(Config.BasePath).Length;
					}
				}

				return 0;
			}
		}

		#endregion

		#region Clear

		/// <summary>
		/// Deletes all errors logged inside the errors folder (BasePath).
		/// </summary>
		public static void Clear()
		{
			using (IsolatedStorageFile file = IsolatedStorageFile.GetUserStoreForApplication())
			{
				if (file.DirectoryExists(Config.BasePath))
				{
					file.DeleteDirectory(Config.BasePath);
				}
			}
		}

		#endregion

		#region Logging

		/// <summary>
		/// Saves the exception and current device details to a file inside BasePath.
		/// </summary>
		/// <param name="ex">The exception object.</param>
		/// <param name="data">Data associated with the exception (optional).</param>
		public static void Log(Exception ex, Object data = null)
		{
			if (ex == null)
			{
				return;
			}

			SessionCount++;

			var path = Config.BasePath + "/" + GetId();
			var contents = Encoding.UTF8.GetBytes(GetErrorString(ex, data));

			using (IsolatedStorageFile file = IsolatedStorageFile.GetUserStoreForApplication())
			{
				if (!file.DirectoryExists(Config.BasePath))
				{
					file.CreateDirectory(Config.BasePath);
				}

				using (IsolatedStorageFileStream stream = file.OpenFile(path, FileMode.Create, FileAccess.ReadWrite))
				{
					stream.Write(contents, 0, contents.Length);
					stream.Close();
				}
			}
		}

		#endregion

		#region Reporting

		/// <summary>
		/// Opens the email client so the user can report the logged errors.
		/// </summary>
		public static void Report()
		{
			if (TotalCount < 1)
			{
				return;
			}

			var email = new EmailComposeTask();
			var body = new StringBuilder();

			using (IsolatedStorageFile file = IsolatedStorageFile.GetUserStoreForApplication())
			{
				foreach (String path in file.GetFileNames(Config.BasePath))
				{
					using (IsolatedStorageFileStream stream = file.OpenFile(path, FileMode.Open, FileAccess.Read))
					{
						using (StreamReader reader = new StreamReader(stream, Encoding.UTF8))
						{
							body.Append(reader.ReadToEnd());
						}

						stream.Close();
					}
				}
			}

			email.Subject = Config.EmailSubject;
			email.To = Config.EmailTo;
			email.Body = body.ToString();

			email.Show();

			if (Config.ClearOnEmailSent)
			{
				Clear();
			}
		}

		#endregion

		#region Helper methods

		/// <summary>
		/// Gets a new error ID, based on the current time.
		/// </summary>
		/// <returns>New error ID.</returns>
		private static String GetId()
		{
			return DateTime.Now.ToString("yyMMddHHmmss-fff");
		}

		/// <summary>
		/// Gets the error string.
		/// </summary>
		/// <param name="ex">Exception object.</param>
		/// <param name="data">Data object associated to the exception (optional).</param>
		/// <returns>Detailed exception and device info.</returns>
		private static String GetErrorString(Exception ex, Object data)
		{
			var result = new StringBuilder();

			result.Append(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
			result.Append(Environment.NewLine);
			result.Append(SEPARATOR);
			result.Append(Environment.NewLine);

			if (data != null)
			{
				result.Append(Messages.Data + ": " + data);
				result.Append(Environment.NewLine);
			}

			result.Append(GetExceptionInfo(ex));

			while (ex.InnerException != null)
			{
				ex = ex.InnerException;

				result.Append(Environment.NewLine);
				result.Append(Messages.InnerException);
				result.Append(Environment.NewLine);
				result.Append(GetExceptionInfo(ex));
			}

			result.Append(SEPARATOR);
			result.Append(Environment.NewLine);

			result.Append(GetDeviceInfo());

			result.Append(Environment.NewLine);
			result.Append(SEPARATOR);

			return result.ToString();
		}

		/// <summary>
		/// Gets the exception details.
		/// </summary>
		/// <param name="ex">Exception object.</param>
		/// <returns>Detailed info about the exception.</returns>
		private static String GetExceptionInfo(Exception ex)
		{
			var result = new StringBuilder();

			result.Append(Messages.Message + ": " + ex.Message);
			result.Append(Environment.NewLine);
			result.Append(Messages.StackTrace + ": " + Environment.NewLine + ex.StackTrace);
			result.Append(Environment.NewLine);

			return result.ToString();
		}

		/// <summary>
		/// Gets the device details.
		/// </summary>
		/// <returns>Detailed info about the device.</returns>
		private static String GetDeviceInfo()
		{
			var result = new StringBuilder();

			result.Append(Messages.AppMemory + ": " + GetMemory(DeviceStatus.ApplicationCurrentMemoryUsage));
			result.Append(" / " + GetMemory(DeviceStatus.ApplicationPeakMemoryUsage));

			if (Config.GetFullDeviceInfo)
			{
				result.Append(Environment.NewLine);
				result.Append(Messages.DeviceName + ": " + DeviceStatus.DeviceManufacturer + " " + DeviceStatus.DeviceName);
				result.Append(Environment.NewLine);
				result.Append(Messages.NetworkAvailable + ": " + NetworkInterface.GetIsNetworkAvailable());
				result.Append(Environment.NewLine);
				result.Append(Messages.PowerSource + ": " + DeviceStatus.PowerSource);
			}

			return result.ToString();
		}

		/// <summary>
		/// Gets the memory amount in MBs.
		/// </summary>
		/// <returns>Memory in MBs.</returns>
		private static String GetMemory(Int64 bytes)
		{
			return Math.Abs(bytes / 1048576) + " MB";
		}

		#endregion

		#region Config class

		public class Config
		{
			#region Properties

			private static String _basePath = "Errors";
			private static String _emailTo = String.Empty;
			private static String _emailSubject = Messages.EmailSubject;
			private static Boolean _getFullDeviceInfo = true;
			private static Boolean _clearOnEmailSent = true;

			/// <summary>
			/// Base path where errors will be stored in the Isolated Storage. Default is "Errors".
			/// </summary>
			public static String BasePath
			{
				get { return _basePath; }
				set
				{
					_basePath = value;

					var lastChar = _basePath.Substring(_basePath.Length - 1, 1);

					if (lastChar == "/" || lastChar == "\\")
					{
						_basePath = _basePath.Substring(0, _basePath.Length - 1);
					}
				}
			}

			/// <summary>
			/// Where to send error reports (email address).
			/// </summary>
			public static String EmailTo
			{
				get { return _emailTo; }
				set { _emailTo = value; }
			}

			/// <summary>
			/// The default email subject when sending error reports.
			/// </summary>
			public static String EmailSubject
			{
				get { return _emailSubject; }
				set { _emailSubject = value; }
			}

			/// <summary>
			/// If true, detailed info about the device will be saved inside the error logs.
			/// </summary>
			public static Boolean GetFullDeviceInfo
			{
				get { return _getFullDeviceInfo; }
				set { _getFullDeviceInfo = value; }
			}

			/// <summary>
			/// If true, all logged errors will be deleted after the Report() method is called.
			/// </summary>
			public static Boolean ClearOnEmailSent
			{
				get { return _clearOnEmailSent; }
				set { _clearOnEmailSent = value; }
			}

			#endregion
		}

		#endregion
	}
}
